from pydantic import BaseModel, Field, create_model, PrivateAttr
from pydantic import main as Daclasses
from typing import Optional, List
from pydantic.fields import FieldInfo

import inspect

OUTLNKEDF = {}

class _AttrField(BaseModel):
    fieldName: str
    desc: str
    dtype: str
    required: bool

class StrField(_AttrField):
	def __init__(self, name, ename, required=True):
		super().__init__(fieldName=name, desc=ename, required=required, dtype="str")

class IntField(_AttrField):
	def __init__(self, name, ename, required=True):
		super().__init__(fieldName=name, desc=ename, required=required, dtype="int")

class FloatField(_AttrField):
	def __init__(self, name, ename, required=True):
		super().__init__(fieldName=name, desc=ename, required=required, dtype="float")

class DateField(_AttrField):
	def __init__(self, name, ename, required=True):
		super().__init__(fieldName=name, desc=ename, required=required, dtype="date")

class List_int_Field(_AttrField):
	def __init__(self, name, ename,  required=True):
		super().__init__(fieldName=name, desc=ename, required=required, dtype="List[int]")

class List_str_Field(_AttrField):
	def __init__(self, name, ename,  required=True):
		super().__init__(fieldName=name, desc=ename, required=required, dtype="List[str]")

class TagModel(BaseModel):
    _doid:Optional[str] = PrivateAttr()

    def setdoid(self, doid):
        self._doid = doid

    def getdoid(self):
        return self._doid

    def obj2dict(self):
        js = self.dict()
        js["doid"] = self.getdoid()
        js["clstype"] = self.__class__.__name__
        if self.getcode() is not None:
            codes = self.getcode()
            if type(codes)==list:
                js["code"] = [ci.dict() for ci in codes]
            else:
                js["code"] = codes.dict()
        # 如果__doc__有配置默认值，则写入默认值
        desc = self.__class__.__doc__ if self.__class__.__doc__ else ""
        # print("obj2dict.__doc__:", desc)
        descarr = desc.split(",")
        for desci in descarr:
            desckv = desci.split(":")
            # print(desckv)
            if len(desckv)>1 and desckv[0].startswith("defval-"): #统一默认值
                f = desckv[0][7:]
                js[f] = desckv[1]
                # print(f, desckv[1])
            if len(desckv)>1 and desckv[0].startswith("ln-"): #关联外部数据
                f = desckv[0][3:]
                kvals = desckv[1].split("#")
                global OUTLNKEDF
                # print("-------------------", OUTLNKEDF["__geodata"])
                if kvals[0] in OUTLNKEDF and kvals[1] in OUTLNKEDF[kvals[0]]:
                    tabdf = OUTLNKEDF[kvals[0]][kvals[1]]
                    # print(tabdf[tabdf[kvals[2]]==js[kvals[2]]][f].values[0])
                    js[f] = tabdf[tabdf[kvals[2]]==js[kvals[2]]][f].values[0]
        return js

    # @field_serializer('doid')
    # def serialize_dt(self, doid: str, _info):
    #     return self._doid
    # pass
    # def setModelType(self, mtype):
    #     self._mtype = mtype;

    # @classmethod
    # def modelType(cls):
    #     return 

def vtype(t):
    if t=="number":
        return "float"
    elif t=="string":
        return "str"
    elif t=="integer":
        return "int"
    elif t=="array":
        return "list"
    return t

CLS = {}

def regModuleClass(metacls):
    # names = [c.__name__ for c in CLS]
    # if metacls.__name__ not in CLS.keys():
        # CLS.append(metacls)
    if metacls is not None:
        CLS[metacls.__name__] = metacls

def _createModuleClass(clsname, desc, basecls, fieldset):
    metacls = create_model(clsname, __base__=basecls, __doc__=desc, **fieldset)
    # print(metacls.schema(), desc)
    # names = [c.__name__ for c in CLS]
    if metacls.__name__ not in CLS.keys():
        # CLS.append(metacls)
        CLS[metacls.__name__] = metacls
    # metacls.__config__.description=desc
    #metacls.__config__.description=desc
    # print('C'*30, metacls.__name__, names)
    return metacls

def makeModuleClass(name, desc, base, fieldset):
    fields = {}
    for field in fieldset:
        fields[field.name] = (eval(vtype(field.type)), Field(None, title=field.title)) #所有都设置不必必填
    return _createModuleClass(name, desc, base, fields)
    # metacls = create_model(name, __base__=base, **fields)
    # names = [c.__name__ for c in CLS]
    # if metacls.__name__ not in names:
    #     CLS.append(metacls)
    # return metacls

def buildClass(meta):
    sch = meta["schema"]
    fields = {}
    for k,v in sch["properties"].getStore().items():
        # print(meta.name, "字段：", k, v)
        fields[k] = (eval(vtype(v["type"])), Field(None, title=v["title"]))
    # print(meta.name, meta.extend)
    pkglst = meta.extend.split(".")
    # print(meta.description, sch["description"])
    return _createModuleClass(meta.name, sch["description"], getModelByName(pkglst[-1]), fields)
    # exec(f"from {'.'.join(pkglst[0:-1])} import {pkglst[-1]}")
    # cls = create_model(meta.name, __base__=getModelByName(pkglst[-1]), **fields)
    # names = [c.__name__ for c in CLS]
    # if cls.__name__ not in names:
    #     CLS.append(cls)
    #     # print("$"*20, CLS)
    # return cls

def getTagModelChildClass(cls):
    cs = inspect.getmro(cls)
    mc = cs[0]
    for c in cs:
        if c==TagModel:
            break
        else:
            mc = c
    # print("xxx>", cls, mc)
    return mc

def findParentClass(cls, findfoo):
    cs = inspect.getmro(cls)
    # print(cs)
    mc = None
    for c in cs:
        if findfoo(c):
            mc = c
            break
            
    # print("xxx>", cls, mc)
    return mc

def getTagModelChildClassName(cls):
    c = getTagModelChildClass(cls)
    return "" if c is None else c.__name__#.capitalize()

def getModelByName(clsname):
    # names = [c.__name__ for c in CLS]
    if clsname in CLS.keys():
        return CLS[clsname]
        # for c in CLS:
        #     if c.__name__==clsname:
        #         return c
    import sys
    ms = [m for m in sys.modules.values()]
    # ls = []
    for mo in ms:
        try:
            #mo = Daclasses
            for name, class_ in inspect.getmembers(mo, inspect.isclass):
                # print(mo, name, class_)
                # ls.append(class_)
                if name == clsname:
                    return class_
        except:
            # continue
            pass
    # print(ls)

def getQueryTagModel(cls):
    cs = inspect.getmro(cls)
    mc = cs[0]
    for c in cs:
        if c==TagModel:
            break
        else:
            mc = c
    # print("xxx>", cls, mc)
    return mc



class RelModel(BaseModel):
    pass

class Graph:
    def __init__(self, drv):
        self.drv = drv

    def createTag(self, mod):
        return self.drv.createTag(mod)

    def updateTag(self, mod):
        return self.drv.updateTag(mod)

    def getTag(self, tagname):
        return self.drv.getTag(tagname)

    def getTags(self):
        return self.drv.getTags()
    
    def getTagTree(self, mod):
        return self.drv.getTagTree(mod)

    def deleteTag(self, tagname):
        return self.drv.delTag(tagname)

    def createRef(self, rmod):
        self.drv.createRef(rmod)

    def getRef(self, refname):
        return self.drv.getRef(refname)

    def createNode(self, nodetype, name=None, tags=None, data=None):
        return self.drv.createNode(nodetype, name, tags, data)

    def updateNode(self, nodeid, **args):
        return self.drv.updateNode(nodeid, **args)

    def getNodes(self, nodetype, **args):
        return self.drv.getNodes(nodetype, **args)
    
    def getNode(self, querynode):
        return self.drv.getNode(querynode)

    def deleteNode(self, nodeid):
        return self.drv.delNode(nodeid)

    def createEdge(self, edgetype, node1name, node2name, **args):
        return self.drv.createEdge(edgetype, node1name, node2name, **args)

    def deleteEdge(self, edgetype, node1name, node2name):
        return self.drv.deleteEdge(edgetype, node1name, node2name)

    def getEdge(self, etype, src, tar):
        return self.drv.getEdge(etype, src, tar)

    def getNexts(self, nid, etype):
        return self.drv.getNexts(nid, etype)
    
    def getLasts(self, nid, etype):
        return self.drv.getLasts(nid, etype)

    def di(self, nid):
        return self.drv.di(nid)

    def query(self, q):
        return self.drv.query(q)
    
    def count(self, tag, filter=None):
        return self.drv.count(tag, filter)

    def plot(self):
        return self.drv.plot()

